
/* Import important libraries */
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;

class SafeCastLogger extends JFrame implements ActionListener, Runnable
{

  /* OS String names */
  private final static String MAC_OS_X = "Mac OS X";
  private final static String WINDOWS = "Windows";
  private final static String LINUX = "Linux";

  /* State names */
  private final static String DRIVEINFO = "Drive Info";
  private final static String CONNECT = "Connect";
  private final static String STARTLOG = "Start Log";
  private final static String STOPLOG = "Stop Log";

	/* To suppress the serialVersionUID warning*/
	private static final long serialVersionUID = 1L;

  // the GUI elements
	JButton connectButton, refreshButton;
  JButton startButton, stopButton, disconnectButton;
  JButton chooseDirButton, quitButton;         // buttons
	JComboBox devModelCombo, gpsSerialCombo, geigerSerialCombo; // drop down menus
  JScrollPane scrollPane;     // console pane
	JTextArea console;          // console text area
  JLabel display;          // current reading display
  JPanel gpsPane;
  JPanel connectDisconnectButtonPane;
  JPanel startStopButtonPane;
  JPanel startLogPane;
  JPanel connectPane;
  JFileChooser dirChooser;
  JTextField driverName;
  JTextField tripInfo;

  // Used by connect function to check if any devices are connected or not
  String[] portList;
	
  // The logger object that's gonna do the real hard work
  Logger logger;

  // A sweet stream for the console messages
  StringOutputStream consoleStream;
  PrintStream history;

  // the directory where to store everything
  File logDir;

  // indicates if we are running or not...
  boolean running;

  // We need a thread for that object
  Thread thread;
	
	SafeCastLogger()
	{
		// Call parent constructor
		super();

    // init the GUI
    initComponents();

    // create console history stream
    consoleStream = new StringOutputStream(50);
    history = new PrintStream(consoleStream, true);

    // create log Directory file
    logDir = new File("./data");
    if (logDir.exists() && logDir.isDirectory() && logDir.canWrite())
    {
      dirChooser.setCurrentDirectory(new File(logDir.getParent()));
      dirChooser.setSelectedFile(logDir);
    } else if (!logDir.exists()) {
      logDir.mkdir();
    }

    // create Logger
    logger = new Logger(5000, logDir, history, history);

    // start to run
    running = true;

    // create and launch thread
    thread = new Thread(this);
    thread.start();

  }

  void initComponents()
  {
		// Create panels
		JPanel mainPanel = new JPanel();
		//mainPanel.setPreferredSize(new Dimension(w,h));
		mainPanel.setLayout(new BorderLayout());
    this.add(mainPanel);

		// Set window parameters
		setTitle("SafeCast Logger");
		//setSize(w, h);
		//setLocation(x, y);
		//setResizable(false);
		setBackground(Color.white);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		setContentPane(mainPanel);

		// GPS models list
		devModelCombo = new JComboBox(Logger.deviceList);
    devModelCombo.setSelectedIndex(Logger.BGEIGIE_MINI);
    devModelCombo.addActionListener(this);

		// update list for serial devices
		portList = SerialLineReader.portList();

    // combo for Geiger
		geigerSerialCombo = new JComboBox(portList);
    if (portList.length > 0)
      geigerSerialCombo.setSelectedIndex(guessBGeigiePort(portList));

    // pane for gps combo
		gpsSerialCombo = new JComboBox(portList);
    if (portList.length > 0)
      gpsSerialCombo.setSelectedIndex(guessGPSPort(portList));
    gpsPane = new JPanel(new CardLayout());
    gpsPane.add(gpsSerialCombo, Logger.deviceList[Logger.BGEIGIE]);
    gpsPane.add(new JPanel(), Logger.deviceList[Logger.BGEIGIE_MINI]);
    CardLayout cl = (CardLayout)gpsPane.getLayout();
    cl.show(gpsPane, (String)devModelCombo.getSelectedItem());

		// Geiger serial port list

    // refresh port list button
    refreshButton = new JButton("Refresh");
    refreshButton.addActionListener(this);

		// Connect button
		connectButton = new JButton("Connect");
		connectButton.addActionListener(this);

		// Disconnect button
		disconnectButton = new JButton("Disconnect");
		disconnectButton.addActionListener(this);

    // Connect/Disconnect pane
    connectDisconnectButtonPane = new JPanel(new CardLayout());
    connectDisconnectButtonPane.add(connectButton, connectButton.getText());
    connectDisconnectButtonPane.add(disconnectButton, disconnectButton.getText());

    	// Start button
		startButton = new JButton("Start");
		startButton.addActionListener(this);

		// Stop button
		stopButton = new JButton("Stop");
		stopButton.addActionListener(this);

    // Connect/Disconnect pane
    startStopButtonPane = new JPanel(new CardLayout());
    startStopButtonPane.add(startButton, startButton.getText());
    startStopButtonPane.add(stopButton, stopButton.getText());

    // Choose dir button
    chooseDirButton = new JButton("Choose directory");
    chooseDirButton.addActionListener(this);

    // File Chooser for data directory
    dirChooser = new JFileChooser();
    dirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

    // create upper panel
    JPanel upperPane = new JPanel();
    upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.LINE_AXIS));
    upperPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    upperPane.add(devModelCombo);
    upperPane.add(Box.createRigidArea(new Dimension(10, 0)));
    upperPane.add(geigerSerialCombo);
    upperPane.add(Box.createRigidArea(new Dimension(10, 0)));
    upperPane.add(gpsPane);
    upperPane.add(Box.createHorizontalGlue());
    upperPane.add(refreshButton);
    upperPane.add(Box.createRigidArea(new Dimension(10, 0)));
    upperPane.add(connectDisconnectButtonPane);
    upperPane.add(Box.createRigidArea(new Dimension(10, 0)));
    upperPane.add(startStopButtonPane);
    upperPane.add(Box.createRigidArea(new Dimension(10, 0)));
    upperPane.add(chooseDirButton);

    // Current readings display
    display = new JLabel("Welcome");
    //display = new JTextArea("Welcome", 4, 20);
    //display.setEditable(false);
    Font f = display.getFont();
    display.setFont(new Font(f.getFontName(), f.getStyle(), (int)(1.5*f.getSize())));

		// Serial output text area
		console = new JTextArea("Welcome to SafeCastLogger. Have a safe trip.", 15, 50);
		console.setEditable(false);
		scrollPane = new JScrollPane(console);
		
		// Driver name text field
		driverName = new JTextField("Please insert driver name",30);

		// Trip info text field
		tripInfo = new JTextField("Please insert trip information & comments",50);
		
    // create text panel
    JPanel textPane = new JPanel();
    textPane.setLayout(new BorderLayout());
    textPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
    textPane.add(driverName, BorderLayout.WEST);
    textPane.add(tripInfo, BorderLayout.CENTER);

    // Merge upper panel and text panel to top panel
    JPanel topPane = new JPanel();
    topPane.setLayout(new BorderLayout());
    topPane.add(upperPane, BorderLayout.NORTH);
    topPane.add(textPane, BorderLayout.CENTER);
    
    // create center panel
    JPanel centerPane = new JPanel();
    centerPane.setLayout(new BoxLayout(centerPane, BoxLayout.LINE_AXIS));
    centerPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
    centerPane.add(display);
    centerPane.add(Box.createHorizontalGlue());
    centerPane.add(scrollPane);

		// Quit button
		quitButton = new JButton("Quit");
		quitButton.addActionListener(this);

    // Create lower pane
    JPanel lowerPane = new JPanel();
    lowerPane.setLayout(new BoxLayout(lowerPane, BoxLayout.LINE_AXIS));
    lowerPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
    lowerPane.add(Box.createHorizontalGlue());
    lowerPane.add(quitButton);

    //Put everything together, using the content pane's BorderLayout.
    mainPanel.add(topPane, BorderLayout.NORTH);
    mainPanel.add(centerPane, BorderLayout.CENTER);
    mainPanel.add(lowerPane, BorderLayout.SOUTH);
		
	}

  void refreshPortList()
  {
    // update port list
		portList = SerialLineReader.portList();
    // remove all items
		gpsSerialCombo.removeAllItems();
		geigerSerialCombo.removeAllItems();
    // put whatever's in the port list now
    for (int i=0 ; i < portList.length ; i++)
    {
      gpsSerialCombo.addItem(portList[i]);
      geigerSerialCombo.addItem(portList[i]);
    }
    // try to guesstimate the port to use
    if (portList.length > 0)
      gpsSerialCombo.setSelectedIndex(guessGPSPort(portList));
    if (portList.length > 0)
      geigerSerialCombo.setSelectedIndex(guessBGeigiePort(portList));
  }

	/* This should automagically make an educated guess about the bGeigie port */
	public int guessBGeigiePort(String[] pl)
	{
    int guess = 0;
    String OS = System.getProperty("os.name");

    // Mac OS X
    if (OS.equals(MAC_OS_X))
    {
      while (guess < pl.length && pl[guess].indexOf("tty.usbserial") == -1)
        guess++;
      if (guess == pl.length && pl.length > 0)
        guess = 0;
    }
    // Windows
    if (OS.equals(WINDOWS) && pl.length > 1)
      guess = 1;
    // Linux (should hopefully be replaced by smarter code...
    if (OS.equals(LINUX) && pl.length > 1)
      guess = 1;

    return guess;
	}

	/* This should automagically make an educated guess about the GPS port */ 
	public int guessGPSPort(String[] pl)
	{
    int guess = 0;
    String OS = System.getProperty("os.name");

    // Mac OS X
    if (OS.equals(MAC_OS_X))
    {
      while (guess < pl.length && pl[guess].indexOf("tty.PL2303") == -1)
        guess++;
      if (guess == pl.length && pl.length > 2)
        guess = 2;
    }
    // Windows
    if (OS.equals(WINDOWS) && pl.length > 1)
      guess = 1;
    // Linux (should hopefully be replaced by smarter code...
    if (OS.equals(LINUX) && pl.length > 1)
      guess = 1;

    return guess;
	}

  void updateDisplay()
  {
    // get readings from the device
    Data d = logger.getData();

    String rate_uSph_s = formatDouble(logger.CPM2uSph(d.getCPM()),3);
    String dose_uS_s = formatDouble(logger.counts2uS(d.getDose()),4);
    String lat_s = formatDouble(d.getLatitude(),6);
    String lon_s = formatDouble(d.getLongitude(),6);

    String s = new String("<html>" + devModelCombo.getSelectedItem());
    s += "#" + d.getDevId() + "<br>";
    s += " Dose Rate: " + rate_uSph_s + "uS/h<br>";
    s += "Dose: " + dose_uS_s + "uS<br>";
    s += "Date: " + d.getDate() + " Time: " + d.getTime() + "<br>";
    s += "Latitude: " + lat_s + " Longitude: " + lon_s + "<br>";
    s += "Altitude: " + d.getAltitude() + "m<br>";

    // Check correctness of the reading
    if (!logger.isConnected())
    {
      s += "<b><font color=red>No device connected!</font></b></html><br>";
    } else {
      if (!logger.getData().isGeigerValid())
        s+= "<b><font color=red>Geiger reading not valid!</font></b><br>";
      if (!logger.getData().isFixAvailable())
        s+= "<b><font color=red>No GPS position available!</font></b><br>";
    }
    s+= "</html>";

    // update display
    //display.replaceRange(s, 0, display.getDocument().getLength());
    display.setText(s);
  }

  static String formatDouble(double d, int nr)
  {
    String ds = Double.toString(d);
    String r;
    int p = ds.indexOf(".");
    int e = ds.toLowerCase().indexOf("e");
    if (p == -1)
      return ds;
    else if (p+nr+1 < ds.length())
      if (e == -1)
        return ds.substring(0,p+nr+1);
      else
        return ds.substring(0,p+nr+1) + ds.substring(e);
    else
      return ds;
  }

  void refreshConsole()
  {
    // refresh
    console.replaceRange(consoleStream.toString(),
        0, console.getDocument().getLength());
    // set position to bottom
    console.setCaretPosition(console.getDocument().getLength());
  }

  public void start()
  {
	if (!logger.isConnected())
	{
		connect();
	}
    logger.setDriverName(driverName.getText());
    logger.setTripInfo(tripInfo.getText());
    logger.start();
    if (logger.isLogging())
    {
      CardLayout cl = (CardLayout)startStopButtonPane.getLayout();
      cl.show(startStopButtonPane, stopButton.getText());
    }

  }

  public void stop()
  {
    logger.stop();
    // change button
    CardLayout cl = (CardLayout)startStopButtonPane.getLayout();
    cl.show(startStopButtonPane, startButton.getText());
  }

	/* method that will connect to serial ports */
	public void connect()
	{
    boolean connected = false;
		// check if lists are full, if not give an error
		if (portList.length == 0)
		{
			history.println("Please connect devices and retry");
		}
		else if (devModelCombo.getSelectedIndex() == Logger.BGEIGIE)
    {
      if (logger.connect((String)geigerSerialCombo.getSelectedItem(),
                            (String)gpsSerialCombo.getSelectedItem()))
      {
        //start();
        connected = true;
      }
    }
    else if (devModelCombo.getSelectedIndex() == Logger.BGEIGIE_MINI)
    {
      if (logger.connect((String)geigerSerialCombo.getSelectedItem()))
      {
        //start();
        connected = true;
      }
    }
    // change button
    if (connected)
    {
      CardLayout cl = (CardLayout)connectDisconnectButtonPane.getLayout();
      cl.show(connectDisconnectButtonPane, disconnectButton.getText());
    }
	}

	/* method that will disconnect from serial ports */
	public void disconnect()
	{
		// dispose of things here
	stop();
	history.println("Disconnecting...");
    logger.disconnect();

    // change button
    CardLayout cl = (CardLayout)connectDisconnectButtonPane.getLayout();
    cl.show(connectDisconnectButtonPane, connectButton.getText());
	}

	/* method that will dispose of everything */
	public void dispose()
	{
		// dispose of things here
		System.out.println("Goodbye...");

    // not running anymore
    running = false;

		// call parent dispose
		super.dispose();

		// exit
		System.exit(0);
	}

	/* Method required by ActionListener */
	public void actionPerformed(ActionEvent e) {
		// get sources object
		Object o = e.getSource();

    if (o == refreshButton)
      refreshPortList();
		if (o == connectButton)
			connect();
		if (o == disconnectButton)
			disconnect();
		if (o == startButton)
			start();
		if (o == stopButton)
			stop();
		if (o == quitButton)
			dispose();		
    if (o == devModelCombo)
    {
      CardLayout cl = (CardLayout)gpsPane.getLayout();
      cl.show(gpsPane, (String)devModelCombo.getSelectedItem());
    }
    if (o == chooseDirButton)
    {
      int returnVal = dirChooser.showOpenDialog(this);
      if(returnVal == JFileChooser.APPROVE_OPTION) {
        history.println("Changed log directory to '" +
            dirChooser.getSelectedFile().getPath() + "'");
        logDir = dirChooser.getSelectedFile();
        logger.setLogDir(logDir);
      }
    }
	}

	/* method that creates and show the GUI */
	private static void createAndShowGUI() {

		//Create and set up the window.
		SafeCastLogger frame = new SafeCastLogger();

		//Display the window.
		frame.pack();
		frame.setVisible(true);
	}

  public void run() {
    System.out.println("Start the thread...");
    history.println("Welcome to SafeCastLogger. Have a safe trip.");
    while (running)
    {
      try
      {
        
        // update display
        updateDisplay();

        // refresh console
        refreshConsole();

        // sleep a bit
        Thread.sleep(200);
      }
      catch (InterruptedException ie)
      {
      }
    }
  }

	/* Main method to launch application */
	public static void main(String[] args)
	{
    //Schedule a job for the event-dispatching thread:
    //creating and showing this application's GUI.
    javax.swing.SwingUtilities.invokeLater(new Runnable()
    {
      public void run() {
        createAndShowGUI(); 
      }
    });
  }
}

